﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Roslyn.Compilers.CSharp;

namespace BITS.PreCompiler.CSharp.Syntax
{
    public partial class Enum
    {
        public partial class Field
        {
            # region • Fields & Properties •

            /// <summary>
            /// Gets or sets the expression.
            /// </summary>
            /// <value>
            /// The expression.
            /// </value>
            public String Expression { get; set; }

            /// <summary>
            /// Gets the expression syntax.
            /// </summary>
            public EqualsValueClauseSyntax EqualsValueClause
            {
                get
                {
                    if (String.IsNullOrEmpty(this.Expression))
                        return null;

                    return Roslyn.Compilers.CSharp.Syntax.EqualsValueClause(
                        Roslyn.Compilers.CSharp.Syntax.ParseExpression(this.Expression));
                }
            }

            # endregion Fields & Properties

            # region • Ctor's •

            /// <summary>
            /// Initializes a new instance of the <see cref="Field"/> class.
            /// </summary>
            public Field() { }

            /// <summary>
            /// Initializes a new instance of the <see cref="Field"/> class.
            /// </summary>
            /// <param name="parent">The parent.</param>
            public Field(IBaseSyntaxNode parent) : base(parent) { }

            /// <summary>
            /// Initializes a new instance of the <see cref="Field"/> class.
            /// </summary>
            /// <param name="enum">The @enum.</param>
            /// <param name="parent">The parent.</param>
            public Field(Field @enum, IBaseSyntaxNode parent = null)
                : this(@enum.GetSyntaxNode(), parent) { }

            /// <summary>
            /// Initializes a new instance of the <see cref="Field"/> class.
            /// </summary>
            /// <param name="identifier">The identifier.</param>
            /// <param name="parent">The parent.</param>
            public Field(String identifier, IBaseSyntaxNode parent = null)
                : base(identifier, parent) { }

            # endregion Ctor's

            # region • Methods •

            /// <summary>
            /// Gets the syntax node.
            /// </summary>
            /// <returns></returns>
            public override EnumMemberDeclarationSyntax GetSyntaxNode()
            {
                return this.BuildMemberDecleration();
            }

            # endregion Methods
        }

        # region • Fields & Properties •

        /// <summary>
        /// Gets or sets the base class.
        /// </summary>
        /// <value>
        /// The base class.
        /// </value>
        public Class BaseClass { get; private set; }

        /// <summary>
        /// Holds the modifiers.
        /// </summary>
        protected readonly List<SyntaxToken> modifiers
            = new List<SyntaxToken> { Roslyn.Compilers.CSharp.Syntax.Token(SyntaxKind.PrivateKeyword) };

        /// <summary>
        /// Gets the modifiers.
        /// </summary>
        public virtual IEnumerable<SyntaxToken> Modifiers
        {
            get { return this.modifiers; }
        }

        /// <summary>
        /// Gets the modifier list.
        /// </summary>
        protected SyntaxTokenList ModifierList
        {
            get
            {
                return Roslyn.Compilers.CSharp.Syntax.TokenList(this.Modifiers);
            }
        }

        /// <summary>
        /// Holds the implementors.
        /// </summary>
        protected readonly List<IType<TypeDeclarationSyntax>> implementors =
            new List<IType<TypeDeclarationSyntax>>();

        /// <summary>
        /// Gets the implementors.
        /// </summary>
        public virtual IEnumerable<IType<TypeDeclarationSyntax>> Implementors
        {
            get { return this.implementors; }
        }

        /// <summary>
        /// Gets the base list.
        /// </summary>
        protected BaseListSyntax BaseList
        {
            get
            {
                if (!this.Implementors.Any())
                    return null;

                return Roslyn.Compilers.CSharp.Syntax.BaseList(
                    types: Roslyn.Compilers.CSharp.Syntax.SeparatedList(
                        nodes: this.Implementors.Select(x => x.TypeSyntax),
                        separators: Enumerable.Repeat(Roslyn.Compilers.CSharp.Syntax.Token(SyntaxKind.CommaToken),
                            this.Implementors.Count() - 1)));
            }
        }

        /// <summary>
        /// Holds the fields.
        /// </summary>
        private readonly List<Field> fields
            = new List<Field>();

        /// <summary>
        /// Gets the fields.
        /// </summary>
        public IEnumerable<Field> Fields
        {
            get { return this.fields; }
        }

        /// <summary>
        /// Gets the members.
        /// </summary>
        public virtual IEnumerable<IMember<EnumMemberDeclarationSyntax>> Members
        {
            get
            {
                foreach (var field in this.Fields)
                {
                    if (!field.Ignore)
                        yield return field;
                }
            }
        }

        /// <summary>
        /// Gets the member list.
        /// </summary>
        protected SeparatedSyntaxList<EnumMemberDeclarationSyntax> MemberList
        {
            get
            {
                var members = this.Members.Select(x => x.GetSyntaxNode().WithTrailingTrivia(
                        Roslyn.Compilers.CSharp.Syntax.ElasticCarriageReturnLineFeed))
                    .ToList();

                if (!members.Any())
                    return Roslyn.Compilers.CSharp.Syntax.SeparatedList<EnumMemberDeclarationSyntax>();

                return Roslyn.Compilers.CSharp.Syntax.SeparatedList(
                    nodes:      members,
                    separators: Enumerable.Repeat(Roslyn.Compilers.CSharp.Syntax.Token(
                            SyntaxKind.CommaToken), members.Count() - 1));
            }
        }

        # endregion Fields & Properties

        # region • Ctor's •

        /// <summary>
        /// Initializes a new instance of the <see cref="Enum"/> class.
        /// </summary>
        public Enum() : base(SyntaxKind.EnumDeclaration)
        {
            this.modifiers = new List<SyntaxToken> {Roslyn.Compilers.CSharp.Syntax.Token(SyntaxKind.PublicKeyword)};
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Enum"/> class.
        /// </summary>
        /// <param name="parent">The parent.</param>
        internal protected Enum(IBaseSyntaxNode parent)
            : base(SyntaxKind.EnumDeclaration, parent)
        {
            this.modifiers = new List<SyntaxToken> { Roslyn.Compilers.CSharp.Syntax.Token(SyntaxKind.PublicKeyword) };
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Enum"/> class.
        /// </summary>
        /// <param name="enum">The @enum.</param>
        /// <param name="parent">The parent.</param>
        public Enum(Enum @enum, IBaseSyntaxNode parent = null)
            : this(@enum.GetSyntaxNode(), parent) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="Enum"/> class.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="parent">The parent.</param>
        public Enum(System.Type type, IBaseSyntaxNode parent = null)
            : base(SyntaxKind.EnumDeclaration, type.Name, parent)
        {
            this.modifiers = new List<SyntaxToken> { Roslyn.Compilers.CSharp.Syntax.Token(SyntaxKind.PublicKeyword) };
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Enum"/> class.
        /// </summary>
        /// <param name="identifier">The identifier.</param>
        /// <param name="parent">The parent.</param>
        public Enum(String identifier, IBaseSyntaxNode parent = null)
            : base(SyntaxKind.EnumDeclaration, identifier, parent)
        {
            this.modifiers = new List<SyntaxToken> { Roslyn.Compilers.CSharp.Syntax.Token(SyntaxKind.PublicKeyword) };
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Enum"/> class.
        /// </summary>
        /// <param name="modifier">The modifier.</param>
        /// <param name="identifier">The identifier.</param>
        /// <param name="parent">The parent.</param>
        public Enum(Modifier modifier, String identifier, IBaseSyntaxNode parent = null)
            : this(parent)
        {
            this.modifiers = ModifierHelper.GetModifers(modifier);

            if (!String.IsNullOrEmpty(identifier))
                this.Identifier = Roslyn.Compilers.CSharp.Syntax.Identifier(identifier);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Enum"/> class.
        /// </summary>
        /// <param name="modifiers">The modifiers.</param>
        /// <param name="identifier">The identifier.</param>
        /// <param name="parent">The parent.</param>
        public Enum(List<SyntaxToken> modifiers, SyntaxToken identifier,
            IBaseSyntaxNode parent = null) : this(parent)
        {
            this.modifiers  = modifiers;
            this.Identifier = identifier;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Enum"/> class.
        /// </summary>
        /// <param name="syntax">The method declaration syntax.</param>
        /// <param name="parent">The parent.</param>
        public Enum(EnumDeclarationSyntax syntax, IBaseSyntaxNode parent = null)
            : base(syntax, parent)
        {
            if (syntax == null)
                return;

            if (syntax.BaseList != null)
                this.ExtractImplementors(syntax.BaseList.Types);

            this.ExtractMembers(syntax.Members);
        }

        # endregion Ctor's

        # region • Methods •

        /// <summary>
        /// Extracts the members.
        /// </summary>
        /// <param name="memberList">The members.</param>
        internal void ExtractMembers(IEnumerable<MemberDeclarationSyntax> memberList)
        {
            foreach (var member in memberList)
            {
                switch (member.Kind)
                {
                    case SyntaxKind.EnumMemberDeclaration:
                    {
                        this.Add(new Field(
                            member as EnumMemberDeclarationSyntax, this));
                    }
                    break;
                }
            }
        }

        /// <summary>
        /// Gets the syntax node.
        /// </summary>
        /// <returns></returns>
        public override EnumDeclarationSyntax GetSyntaxNode()
        {
            return this.BuildEnumDecleration();
        }

        /// <summary>
        /// Adds the specified class.
        /// </summary>
        /// <param name="enumField">The enum field.</param>
        public Enum Add(Field @enumField)
        {
            if (@enumField == null)
                return this;

            @enumField.Parent = this;
            this.fields.Add(@enumField);

            return this;
        }

        /// <summary>
        /// Adds the specified using list.
        /// </summary>
        /// <param name="enumFieldList">The enum field list.</param>
        public Enum Add(IEnumerable<Field> @enumFieldList)
        {
            if (@enumFieldList == null)
                return this;

            foreach (var @enumField in @enumFieldList)
                this.Add(@enumField);

            return this;
        }

        /// <summary>
        /// Implements the specified type.
        /// </summary>
        /// <param name="type">The type.</param>
        public Enum Implements(Class @type)
        {
            if (@type == null)
                return this;

            if (this.BaseClass == null)
            {
                this.BaseClass = @type;
                this.implementors.Add(@type);

                return this;
            }

            var result = this.implementors
                .Where(x => x.Identifier == this.BaseClass.Identifier)
                .ToList();

            if (result.Any())
                this.implementors.Remove(result.Single());

            this.BaseClass = @type;
            this.implementors.Add(@type);

            return this;
        }

        public Enum WithBase(String @type)
        {
            return this.Implements(new Class(@identifier: @type));
        }

        public Enum WithBase(Class @type)
        {
            return this.Implements(@type);
        }

        public Enum MakePartial(Boolean add = true)
        {
            var partial = Roslyn.Compilers.CSharp.Syntax.Token(SyntaxKind.PartialKeyword);

            if (!this.Modifiers.Contains(partial))
            {
                if (add)
                    this.modifiers.Add(partial);
                else
                    this.modifiers.Remove(partial);
            }

            return this;
        }

        public Enum ConvertToPartial()
        {
            var partial = this.CreatePartialHull();

            this.MakePartial();

            return partial;
        }

        public IEnumerable<Enum> GetPartials()
        {
            yield return this.CreatePartialHull();
            yield return this.CreatePartialClone();
        }

        /// <summary>
        /// Gets an empty partial class hull based on this class.
        /// </summary>
        /// <returns></returns>
        public Enum CreatePartialHull()
        {
            var @class = this.CreatePartialClone();

            @class.ClearAttributeDeclarations();
            @class.ClearMembers();
            @class.ClearImplementors();

            return @class;
        }

        /// <summary>
        /// Clears the implementors.
        /// </summary>
        public virtual void ClearImplementors()
        {
            this.implementors.Clear();
        }

        public Enum CreatePartialClone()
        {
            return this.Clone().MakePartial();
        }

        /// <summary>
        /// Clears all enum memebers.
        /// </summary>
        public virtual void ClearMembers()
        {
            this.fields.Clear();
        }

        /// <summary>
        /// Clones this enum.
        /// </summary>
        /// <returns></returns>
        public Enum Clone()
        {
            return new Enum(this, this.Parent);
        }

        # region • Methods - IEnumerable •

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> 
        /// that can be used to iterate through the collection.
        /// </returns>
        IEnumerator<Field> IEnumerable<Field>.GetEnumerator()
        {
            return this.Fields.GetEnumerator();
        }

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> 
        /// that can be used to iterate through the collection.
        /// </returns>
        IEnumerator<IEnumerable<Field>> IEnumerable<IEnumerable<Field>>.GetEnumerator()
        {
            var @this = this as IEnumerable<Field>;

            yield return @this;
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> 
        /// object that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        IEnumerator IEnumerable.GetEnumerator()
        {
            foreach (var memeber in this.Members)
                yield return memeber;
        }

        # endregion Methods - IEnumerable

        # endregion Methods
    }
}
